/*
 * Throwing model for CS181 darts game
 */

#include <stdio.h>
#include <math.h>
#include <stdlib.h>

#include "darts.h"

#define PI (2 * asin(1))

/*
 * The throwing model takes as input a target location.  
 * It proceeds as follows:
 * (1) Convert the dartboard location into a geometric location in polar coordinates.
 * (2) Convert the polar coordinates into rectangular coordinates.
 * (3) Add bias and noise to the rectangular coordinates.
 * (4) Convert back into polar coordinates.
 * (5) Convert back into a dartboard location, and return.
 *
 * The bias is a constant factor added to each throw, modeling consistent
 * tendencies of a thrower to aim incorrectly.
 * The noise is an added jitter factor randomly added to a throw, modeling
 * tendencies of a thrower to wobble.
 *
 * A thrower is therefore characterized by the bias and the degree of wobble.
 * These are captured in the bias and wobble variables, which are set randomly
 * by init_thrower. 
 *
 * A simpler throwing model is also provided.
 * In this model, the wedge hit is distributed as follows: 
 * With probability 0.1: two to the left of the target wedge
 * With probability 0.2: one to the left of the target wedge
 * With probability 0.4: the target wedge
 * With probability 0.2: one to the right of the target wedge
 * With probability 0.1: two to the right of the target wedge
 * 
 * The ring hit is similarly distributed.  Let the target ring be i.
 * An error from -2 to +2 is selected, with probabilities as above.
 * The result ring is abs(i+error).
 */

/* Polar and rectangular coordinates */

typedef struct {
  float theta;
  float r;
} polar;

typedef struct {
  float x;
  float y;
} rectangular;

polar rectangular_to_polar(rectangular rect) {
  polar result;

  result.r = sqrt(rect.x * rect.x + rect.y * rect.y);
  result.theta = atan2(rect.y, rect.x);
  if (result.theta<0) {
    result.theta = 2 * PI + result.theta;
  } 

  return result;
}

rectangular polar_to_rectangular(polar pol) {
  rectangular result;

  result.y = pol.r * sin(pol.theta);
  result.x = pol.r * cos(pol.theta);

  return result;
}

/* Generate a uniform [0,1] random number */
float ranf() {
  return (float)rand()/(float)RAND_MAX;
}

/* 
 * Generate a random integer error from -2 to 2 with the probabilities
 * 0.1 : -2
 * 0.2 : -1
 * 0.4 : 0
 * 0.2 : 1
 * 0.1 : 2
 */
int rane() {
  float f;

  f = ranf();

  if (f<0.1) {
    return -2;
  } else if (f<0.3) {
    return -1;
  } else if (f<0.7) {
    return 0;
  } else if (f<0.9) {
    return 1;
  } else {
    return 2;
  }
}

/* Generate two independent standard normal random numbers */
rectangular box_muller() {
  float x1, x2, w;
  rectangular result;

  do {
    x1 = 2.0 * ranf() - 1.0;
    x2 = 2.0 * ranf() - 1.0;
    w = x1 * x1 + x2 * x2;
  } while ( w >= 1.0 );

  w = sqrt( (-2.0 * log( w ) ) / w );
  result.x = x1 * w;
  result.y = x2 * w;

  return result;
}

char simple_flag;

void use_simple_thrower() {
  simple_flag = 1;
}

/* Parameters of the throwing model */
rectangular bias;   /* Constant factor by which aim is distorted */
rectangular wobble; /* Degree of jitter */

/* The throwing model */
rectangular throwing_model(rectangular rect) {
  rectangular result, noise;

  noise = box_muller();

  result.x = rect.x + bias.x + noise.x * wobble.x;
  result.y = rect.y + bias.y + noise.y * wobble.y;

  return result;
}

/* 
 * Determine the parameters of the throwing model.
 * The bias is drawn from a normal distribution, 
 * whose standard deviation is given by the constant BIAS_SD.
 * The wobble is uniform, in the range WOBBLE_MIN to WOBBLE_MAX.
 */
#define BIAS_SD 0.3
#define WOBBLE_MIN 0.5
#define WOBBLE_MAX 0.8

void init_thrower() {
  rectangular b,w;

  simple_flag = 0;

  b = box_muller();
  w = box_muller();

  bias.x = b.x * BIAS_SD;
  bias.y = b.y * BIAS_SD;

  w.x = ranf();
  w.y = ranf();

  wobble.x = WOBBLE_MIN + w.x * (WOBBLE_MAX - WOBBLE_MIN);
  wobble.y = WOBBLE_MIN + w.y * (WOBBLE_MAX - WOBBLE_MIN);


    printf("Thrower bias = (%f,%f)\n", bias.x, bias.y);
    printf("Thrower wobble = (%f,%f)\n", wobble.x, wobble.y);
	
}

/* Convert between dartboard locations and polar coordinates */

polar location_to_polar(location loc) {
  polar result;

  result.theta = angles[loc.wedge] * 2 * PI / NUM_WEDGES;
  switch (loc.ring) {
  case CENTER : result.r = 0; break;
  case INNER_RING : result.r = 0.3; break;
  case FIRST_PATCH : result.r = 1.1; break;
  case MIDDLE_RING : result.r = 1.9; break;
  case SECOND_PATCH : result.r = 2.5; break;
  case OUTER_RING : result.r = 3.1; break;
  default : result.r = 999999;
  }

  return result;
}

location polar_to_location(polar pol) {
  location result;
  float scaled_angle;
  int adjusted_angle;
  float r;

  r = pol.r;
  /* scale the angle from 0 to NUM_WEDGES */
  scaled_angle = (pol.theta * NUM_WEDGES) / (2 * PI);
  /* adjust for the fact that the n-th wedge goes from n-0.5 to n+0.5 */
  adjusted_angle = (int)(scaled_angle + 0.5) % NUM_WEDGES;
  result.wedge = wedges[(int)(adjusted_angle)];

  if (r<0.2) {
    result.ring = CENTER;
  } else if (r<0.4) {
    result.ring = INNER_RING;
  } else if (r<1.8) {
    result.ring = FIRST_PATCH;
  } else if (r<2.0) {
    result.ring = MIDDLE_RING;
  } else if (r<3.0) {
    result.ring = SECOND_PATCH;
  } else if (r<3.2) {
    result.ring = OUTER_RING;
  } else {
    result.ring = MISS;
  }

  return result;
}

/*
 * The simpler throwing model.
 * Call use_simple_thrower to start using it.
 */

location simple_throwing_model(location target) {
  int e1, e2;
  int angle1, angle2,angle3;
  location result;

  e1 = rane();
  e2 = rane();

  angle1 = angles[target.wedge];
  angle2 = angle1+e1;
  if (angle2<0) {
    angle3 = angle2 + NUM_WEDGES;
  } else if (angle2>=NUM_WEDGES) {
    angle3 = angle2 - NUM_WEDGES;
  } else {
    angle3 = angle2;
  }
  result.wedge = wedges[angle3];

  result.ring = abs (target.ring + e2);
  if (result.ring > 6) result.ring = 6;

  return result;
}

/* The top-level throw function */

location throw(location target) {
  polar pol1, pol2;
  rectangular rect1, rect2;
  location result;

  if (simple_flag) {
    return simple_throwing_model(target);
  } else {
    pol1 = location_to_polar(target);
    rect1 = polar_to_rectangular(pol1);
    rect2 = throwing_model(rect1);
    pol2 = rectangular_to_polar(rect2);
    result = polar_to_location(pol2);

    return result;
  }
}
